import type {
	UseTreeParam,
	injectionFnObj,
	TreeNodeMeta,
	TreeKey,
} from './type';
import type {
	LazyLoadStatus,
} from '../../type';
import {
	customRef,
	computed,
	watch,
	nextTick,
	onMounted,
} from 'vue';
import {
	isBaseType,
	createCache,
	createGetter,
} from '../../util';

//树的核心功能: 打平树结构, 展开, 懒加载
export default function<T extends Record<string,any>,R>(
	{
		objKey,
		childrenKey,
		accordion,
		data,
		lazyProp,
		expandableProp,
		expandProp,
		leafProp,
		expandedSet,
		visibleFilterFn,
		lazyLoadFn,
		injectionLoopFn,
	}:UseTreeParam<T,R>
){
	//region 懒加载状态缓存
	const lazyCache=customRef((track,trigger) => {
		const cache=new Map();
		const obj={
			get(key:TreeKey<T>):LazyLoadStatus<boolean>|undefined{
				track();
				return cache.get(key);
			},
			set(key:TreeKey<T>,status:LazyLoadStatus<boolean>){
				cache.set(key,status);
				trigger();
			},
			clear(){
				cache.clear();
				trigger();
			},
		};
		return {
			get(){return obj;},
			set(){},
		};
	}).value;
	//endregion
	//region 依赖objKey相关方法
	const objKeyFns=computed(() => {
		const objKeyVal=objKey.value;
		let getKeyFn:(d:T) => TreeKey<T>;
		if(objKeyVal!=null){
			getKeyFn=createGetter(objKeyVal);
		}else{
			getKeyFn=(raw) => raw;
		}
		lazyCache.clear();
		return {
			getKeyFn,
		};
	});
	//endregion
	//region 依赖childrenKey相关方法
	const childrenKeyFns=computed(() => {
		const childrenKeyVal=childrenKey.value;
		let getChildrenFn:(d:T) => T[];
		let setChildrenFn:(d:T,c:T[]) => void;
		if(typeof childrenKeyVal==='string'){
			getChildrenFn=(raw) => {
				return raw[childrenKeyVal];
			};
			setChildrenFn=(raw,children) => {
				// @ts-ignore
				raw[childrenKeyVal]=children;
			};
		}else{
			({
				get:getChildrenFn,
				set:setChildrenFn,
			}=childrenKeyVal);
		}
		return {
			getChildrenFn,
			setChildrenFn,
		};
	});
	//endregion
	//region 数据变化, 执行相关副作用
	watch(() => data.value,() => {
		lazyCache.clear();
		expandByPropMark();
	});
	//endregion
	onMounted(expandByPropMark);
	//region 打平树结构, 并缓存信息
	const preData=computed(() => {
		const {
			getKeyFn,
		}=objKeyFns.value;
		const {
			getChildrenFn,
		}=childrenKeyFns.value;
		const getKeyStrFn:(key:TreeNodeMeta<T,R>) => string|number=objKey.value==null
			?(meta) => {
				return meta.index;
			}
			:(meta) => {
				return meta.key as string|number;
			};
		const {
			get:getMeta,
			set:setMeta,
			values:toArr,
		}=createCache<TreeKey<T>,TreeNodeMeta<T,R>>();
		let index=0;
		const travel=(raws:T[],indent:number,parent?:TreeNodeMeta<T,R>):TreeNodeMeta<T,R>[] => {
			const list=new Array(raws.length);
			for(let i=0;i<raws.length;++i){
				const raw=raws[i];
				const key=getKeyFn(raw);
				const children=getChildrenFn(raw);
				const hasChildren:boolean=(Array.isArray(children)&&children.length>0)===true;
				let lazy:undefined|boolean|LazyLoadStatus<boolean>=raw[lazyProp];
				let lazyNotLoaded=false;
				let lazyLoading=false;
				//region 是懒加载, 就拉取存储的懒加载状态
				if(lazy){
					const _lazy=lazyCache.get(key);
					lazyNotLoaded=_lazy!=='loaded';
					if(_lazy){
						lazy=_lazy;
						lazyLoading=_lazy!=='failed'&&lazyNotLoaded;
					}
				}
				//endregion
				const validChildren:boolean=hasChildren&&!lazyNotLoaded;
				const notLeaf:boolean=validChildren||lazyNotLoaded||raw[leafProp]===false;
				const expandable:boolean=notLeaf&&raw[expandableProp]!==false;
				const meta:any={
					raw,
					index:index++,
					indent,
					parent,
					hasChildren,
					validChildren,
					notLeaf,
					key,
					lazy,
					lazyNotLoaded,
					lazyLoading,
					expandable,
				};
				meta.keyStr=getKeyStrFn(meta);
				setMeta(key,meta);
				if(validChildren){
					meta.children=travel(children,indent+1,meta);
				}
				list[i]=meta;
			}
			return list;
		};
		const root=travel(data.value,0);
		return {
			root,
			allFlatList:toArr(),
			getMeta,
		};
	});
	//endregion
	//region 遍历并修改树状态
	const treeInfo=computed(() => {
		const {
			root,
			allFlatList,
			getMeta,
		}=preData.value;
		expandedSet.track();
		let injection:injectionFnObj<T,R>;
		if(injectionLoopFn){
			injection=injectionLoopFn();
		}else{
			injection={};
		}
		const {initFn}=injection;
		const travel=(metas:TreeNodeMeta<T,R>[]) => {
			for(let i=0;i<metas.length;++i){
				const meta=metas[i];
				const {
					key,
					parent,
					notLeaf,
					lazyNotLoaded,
					children,
				}=meta;
				meta.visible=parent==null||(parent.visible&&parent.expanded);
				meta.expanded=notLeaf&&!lazyNotLoaded?expandedSet.has(key):false;
				initFn&&initFn(meta);
				if(children){
					travel(children);
				}
			}
		};
		travel(root);
		return {
			root,
			allFlatList,
			visibleFlatList:allFlatList.filter(visibleFilterFn),
			getMeta,
		};
	});
	//endregion
	//region 通过key或者raw, 获取节点key
	function getKeyByKeyOrRaw(keyOrRaw:TreeKey<T>):TreeKey<T>{
		if(isBaseType(keyOrRaw)){
			return keyOrRaw;
		}
		return objKeyFns.value.getKeyFn(keyOrRaw);
	}
	//endregion
	//region 通过key或者raw获取节点信息
	function getMetaByKeyOrRaw(keyOrRaw:TreeKey<T>):TreeNodeMeta<T,R>|undefined{
		return treeInfo.value.getMeta(getKeyByKeyOrRaw(keyOrRaw));
	}
	//endregion
	//region 同步设置展开状态(当前节点无限制)
	function _syncSetExpand(
		meta:TreeNodeMeta<T,R>,
		state:boolean,
		accordion:boolean,
		expandFn:(node:TreeKey<T>) => void,
		collapseFn:(node:TreeKey<T>) => void,
	):void{
		if(state&&accordion){
			const metas=meta.parent?meta.parent.children!:treeInfo.value.root;
			for(let i=0;i<metas.length;++i){
				const m=metas[i];
				if(m.expandable&&meta!==m){
					if(m.expanded){
						collapseFn(m.key);
					}
				}
			}
		}
		if(state){
			if(!meta.expanded){
				expandFn(meta.key);
			}
		}else{
			if(meta.expanded){
				collapseFn(meta.key);
			}
		}
	}
	//endregion
	//region 开始懒加载并设置展开(当前节点无限制), Promise表示懒加载是否完成, 如果是第一次且成功则返回true
	function _lazyLoadAndExpand(
		meta:TreeNodeMeta<T,R>,
		accordion:boolean,
	):Promise<boolean>{
		if(meta.lazy instanceof Promise){
			return meta.lazy;
		}
		const promise=(async function(){
			if(lazyLoadFn){
				try{
					const children=await lazyLoadFn(meta);
					lazyCache.set(meta.key,'loaded');
					childrenKeyFns.value.setChildrenFn(meta.raw,children);
					await nextTick();
					const m=treeInfo.value.getMeta(meta.key)!;
					if(m.notLeaf){
						const collapses:TreeKey<T>[]=[];
						_syncSetExpand(
							m,
							true,
							accordion,
							(key) => {
								expandedSet.add(key);
							},
							(key) => {
								collapses.push(key);
							}
						);
						if(collapses.length){
							expandedSet.deleteAll(collapses);
						}
						return true;
					}
				}catch{
					lazyCache.set(meta.key,'failed');
				}
				return false;
			}
			return new Promise<boolean>(() => {});
		})();
		lazyCache.set(meta.key,promise.then(() => false));
		return promise;
	}
	//endregion
	//region 设置行的展开状态, 返回操作是否成功
	function setExpand(
		{
			state,
			keyOrRaw,
			meta,
			isAccordion=accordion.value,
		}:{
			state:boolean;
			keyOrRaw?:TreeKey<T>;
			meta?:TreeNodeMeta<T,R>;
			isAccordion?:boolean;
		}
	):Promise<boolean>{
		if(!meta){
			meta=getMetaByKeyOrRaw(keyOrRaw!);
		}
		if(!(meta&&meta.expandable)) return Promise.resolve(false);
		if(meta.lazyNotLoaded){
			if(state){
				return _lazyLoadAndExpand(meta,isAccordion);
			}
		}else{
			const collapses:TreeKey<T>[]=[];
			_syncSetExpand(
				meta,
				state,
				isAccordion,
				(key) => {
					expandedSet.add(key);
				},
				(key) => {
					collapses.push(key);
				},
			);
			if(collapses.length){
				expandedSet.deleteAll(collapses);
			}
		}
		return Promise.resolve(true);
	}
	//endregion
	//region 批处理节点展开状态(无限制)
	function _batch(
		callback:(dealMetaFn:(meta:TreeNodeMeta<T,R>,state:boolean,accordion:boolean) => void) => void,
	):Promise<void>{
		const all:Promise<any>[]=[];
		const expands:Set<TreeKey<T> >=new Set();
		const expandFn=(key:TreeKey<T>) => {
			expands.add(key);
		};
		const collapses:Set<TreeKey<T> >=new Set();
		const collapseFn=(key:TreeKey<T>) => {
			collapses.add(key);
		};
		//region 节点处理函数
		function dealMetaFn(meta:TreeNodeMeta<T,R>,state:boolean,accordion:boolean):void{
			if(meta.lazyNotLoaded){
				if(state){
					all.push(_lazyLoadAndExpand(meta,accordion));
				}
			}else{
				_syncSetExpand(
					meta,
					state,
					accordion,
					expandFn,
					collapseFn,
				);
			}
		}
		//endregion
		callback(dealMetaFn);
		if(collapses.size){
			expandedSet.deleteAll(collapses);
		}
		if(expands.size){
			expandedSet.addAll(expands);
		}
		return all.length?Promise.all(all).then(() => undefined):Promise.resolve();
	}
	//endregion
	//region 批量设置行的展开状态
	function setExpands(
		{
			state,
			keyOrRaws,
			isAccordion=accordion.value,
		}:{
			state:boolean;
			keyOrRaws:Array<TreeKey<T>>;
			isAccordion?:boolean;
		}
	):Promise<void>{
		return _batch((dealMetaFn) => {
			const getKeyFn=objKeyFns.value.getKeyFn;
			const getMeta=treeInfo.value.getMeta;
			for(let i=0;i<keyOrRaws.length;++i){
				let keyOrRaw=keyOrRaws[i];
				if(!isBaseType(keyOrRaw)){
					keyOrRaw=getKeyFn(keyOrRaw);
				}
				const meta=getMeta(keyOrRaw);
				if(meta&&meta.expandable){
					dealMetaFn(meta,state,isAccordion);
				}
			}
		});
	}
	//endregion
	//region 设置所有行的展开状态
	function setExpandsAll(
		{
			state,
		}:{
			state:boolean;
		}
	):Promise<void>{
		return _batch((dealMetaFn) => {
			const {
				allFlatList,
			}=preData.value;
			for(let i=0;i<allFlatList.length;++i){
				const meta=allFlatList[i];
				if(meta.expandable){
					dealMetaFn(meta,state,false);
				}
			}
		});
	}
	//endregion
	//region 根据节点属性标记, 设置展开(标记节点无限制)
	function expandByPropMark():Promise<void>{
		return _batch((dealMetaFn) => {
			const {
				allFlatList,
			}=preData.value;
			for(let i=0;i<allFlatList.length;++i){
				const meta=allFlatList[i];
				if(meta.notLeaf&&!meta.expanded&&meta.raw[expandProp]===true){
					dealMetaFn(meta,true,false);
				}
			}
		});
	}
	//endregion
	//region 展开到当前节点
	function expandToNode(
		{
			keyOrRaw,
			meta,
			isAccordion=accordion.value,
		}:{
			keyOrRaw?:TreeKey<T>;
			meta?:TreeNodeMeta<T,R>;
			isAccordion?:boolean;
		}
	):void{
		if(!meta){
			meta=getMetaByKeyOrRaw(keyOrRaw!);
			if(!meta) return;
		}
		if(!meta.visible){
			const expands:TreeKey<T>[]=[];
			const expandFn=(key:TreeKey<T>) => {
				expands.push(key);
			};
			const collapses:TreeKey<T>[]=[];
			const collapseFn=(key:TreeKey<T>) => {
				collapses.push(key);
			};
			let curMeta=meta.parent;
			while(curMeta){
				if(curMeta.expandable&&!curMeta.expanded){
					_syncSetExpand(
						curMeta,
						true,
						isAccordion,
						expandFn,
						collapseFn,
					);
				}
				curMeta=curMeta.parent;
			}
			if(collapses.length){
				expandedSet.deleteAll(collapses);
			}
			if(expands.length){
				expandedSet.addAll(expands);
			}
		}
	}
	//endregion
	//region 获取展开的行数据
	type GetExpandedParam={
		expanded?:Array<TreeKey<T>>|Set<TreeKey<T>>;
		raw?:boolean;
	}
	type GetExpandedReturn<C>=C extends {raw:false}?TreeNodeMeta<T,R>:T;
	function getExpanded<C extends GetExpandedParam>(
		{
			expanded=expandedSet,
			raw=true,
		}:C
	):GetExpandedReturn<C>[]{
		const getKeyFn=objKeyFns.value.getKeyFn;
		const getMeta=treeInfo.value.getMeta;
		const getRow=(keyOrRaw:TreeKey<T>):GetExpandedReturn<C> => {
			if(!isBaseType(keyOrRaw)){
				keyOrRaw=getKeyFn(keyOrRaw);
			}
			const meta=getMeta(keyOrRaw);
			// @ts-ignore
			return raw?meta.raw:meta;
		};
		if(Array.isArray(expanded)){
			const res=new Array(expanded.length);
			for(let i=0;i<expanded.length;++i){
				res[i]=getRow(expanded[i]);
			}
			return res;
		}else{
			const res=new Array(expanded.size);
			let i=0;
			for(const key of expanded){
				res[i++]=getRow(key);
			}
			return res;
		}
	}
	//endregion
	return {
		getKeyByKeyOrRaw,
		treeInfo,
		getMetaByKeyOrRaw,
		setExpand,
		setExpands,
		setExpandsAll,
		expandByPropMark,
		expandToNode,
		getExpanded,
		clearLazyCache:lazyCache.clear,
	};
}